/*  ----------------------------------------------------------------------------
 *  Arello Mobile
 *  Fast 3d Engine
 *  ----------------------------------------------------------------------------
 * Except where otherwise noted, this work is licensed under
 * http://creativecommons.org/licenses/by/3.0
 */

#include "Light.h"
//#include "Root.h"
#include <lib3d/hard/Hardware.h>
#include <xml/Stream.h>
#include <Converter.h>
#include <safe_new.h>

#include <boost/bind.hpp>

namespace liba
{
namespace lib3d
{
namespace node
{

const Atom Light::node_name( "Light" );
const Atom Light::ambient_name( "Ambient" );
const Atom Light::diffuse_name( "Diffuse" );
const Atom Light::specular_name( "Specular" );
const Atom Light::intensity_name( "Intensity" );

Light::Light( const Atom & name, Node * parent )
	: Node( name, parent )
	, intensity(
		boost::bind(&Light::GetIntensity, this),
		boost::bind(&Light::SetIntensity, this, _1))
	, ambient(
		boost::bind(&Light::GetAmbient, this),
		boost::bind(&Light::SetAmbient, this, _1))
	, diffuse(
		boost::bind(&Light::GetDiffuse, this),
		boost::bind(&Light::SetDiffuse, this, _1))
	, specular(
		boost::bind(&Light::GetSpecular, this),
		boost::bind(&Light::SetSpecular, this, _1))
{
	this->RegisterProperty(intensity_name, &this->intensity);
	this->RegisterProperty(ambient_name, &this->ambient);
	this->RegisterProperty(diffuse_name, &this->diffuse);
	this->RegisterProperty(specular_name, &this->specular);
	node_type = node_name;
}

Light::Light(const Light& rhs)
	: Node(rhs)
	, hardware_light(rhs.hardware_light)
	, intensity(
		boost::bind(&Light::GetIntensity, this),
		boost::bind(&Light::SetIntensity, this, _1))
	, ambient(
		boost::bind(&Light::GetAmbient, this),
		boost::bind(&Light::SetAmbient, this, _1))
	, diffuse(
		boost::bind(&Light::GetDiffuse, this),
		boost::bind(&Light::SetDiffuse, this, _1))
	, specular(
		boost::bind(&Light::GetSpecular, this),
		boost::bind(&Light::SetSpecular, this, _1))
{
	this->RegisterProperty(intensity_name, &this->intensity);
	this->RegisterProperty(ambient_name, &this->ambient);
	this->RegisterProperty(diffuse_name, &this->diffuse);
	this->RegisterProperty(specular_name, &this->specular);
}

Light::~Light()
{}

Light * Light::clone()
{
	return new Light(*this);
}

bool Light::on_attribute( xml::Provider * prov, const std::string & name, const std::string & value )
{
	if ( name == "Diffuse" )
	{
		return converter::convert( value, &hardware_light.diffuse );
	}
	if ( name == "Ambient" )
	{
		return converter::convert( value, &hardware_light.ambient );
	}
	if ( name == "Specular" )
	{
		return converter::convert( value, &hardware_light.specular );
	}
	if ( name == "Falloff" )
	{
		return converter::convert( value, &hardware_light.falloff );
	}
	if ( name == "Phi" )
	{
		return converter::convert( value, &hardware_light.phi );
	}
	if ( name == "Theta" )
	{
		return converter::convert( value, &hardware_light.theta );
	}
	if ( name == "Type" )
	{
		return converter::convert( value, &hardware_light.type );
	}
	if ( name == "Intensity" )
	{
		return converter::convert( value, &hardware_light.intensity );
	}
	if ( name == "Attenuation" )
	{
		return converter::convert( value, &hardware_light.attenuation );
	}
	if ( name == "ShadowPriority" )
	{
		return converter::convert( value, &hardware_light.shadow_priority );
	}
	return Node::on_attribute( prov, name, value );
}

bool Light::on_node( xml::Provider * prov, const std::string & name )
{
	return Node::on_node( prov, name );
}

bool Light::on_end_node( xml::Provider * prov )
{
//	get_root()->remove_light( this );
//	if( is_infinite() )
//		get_root()->add_infinite_light( this );
//	else
//		get_root()->add_light( this );
	on_change_parameters();
	return true;
}

double Light::GetIntensity()
{
	return hardware_light.intensity;
}

void Light::SetIntensity(double value)
{
	hardware_light.intensity = value;
	on_change_parameters();
}

const Color& Light::GetAmbient()
{
	return hardware_light.ambient;
}

void Light::SetAmbient(const Color& value)
{
	hardware_light.ambient = value;
}

const Color& Light::GetDiffuse()
{
	return hardware_light.diffuse;
}

void Light::SetDiffuse(const Color& value)
{
	hardware_light.diffuse = value;
}

const Color& Light::GetSpecular()
{
	return hardware_light.specular;
}

void Light::SetSpecular(const Color& value)
{
	hardware_light.specular = value;
}

bool Light::is_infinite()const
{
	return hardware_light.type == lib3d::Light::AMBIENT || hardware_light.type == lib3d::Light::DIRECTIONAL;
}

void Light::on_change_parameters()
{
/*	range = 0;
	if( is_infinite() )
		return;
	const double limit = 0.02;
	const double l = hardware_light.linear_attenuation;
	const double q = hardware_light.quadratic_attenuation;
	const double ca = hardware_light.constant_attenuation;
	const double c = ca - hardware_light.intensity/limit;
	if( q != 0 )
	{
		const double descr = l*l - 4*q*c;
		if( descr < 0 )
			return;
		range = (-l + sqrt(descr))/2/q;
	}
	else
	{
		range = -l / c;
	}*/
}

void Light::render_to_list( hard::Hardware * hardware, hard::RenderList * render_list )
{
	if( hardware_light.intensity > 0.01 ) // BUG constant in code
	{
		hard::RenderLight render_light;

		render_light.shadow_priority = hardware_light.shadow_priority;
		switch( hardware_light.type )
		{
		case lib3d::Light::POINT:
			{
				render_light.is_directional = false;
				render_light.diffuse = hardware_light.diffuse;
				render_light.specular = hardware_light.specular;
				render_light.intensity = hardware_light.intensity;

				render_light.world_position = GetGlobalMatrix() * Vector(0,0,0);
				render_light.attenuation = hardware_light.attenuation;

				render_list->lights.push_back( render_light );
				break;
			}
		case lib3d::Light::DIRECTIONAL:
			{
				render_light.is_directional = true;
				render_light.diffuse = hardware_light.diffuse;
				render_light.specular = hardware_light.specular;
				render_light.intensity = hardware_light.intensity;

				render_light.world_direction = Vector(0,0,-1) * GetGlobalMatrix();
				render_list->lights.push_back( render_light );
				break;
			}
		case lib3d::Light::AMBIENT:
			{
				render_list->ambient_color += hardware_light.ambient * hardware_light.intensity;
				break;
			}
		}
	}
	return Node::RenderToList( hardware, render_list );
}

} // namespace node
} // namespace lib3d
} // namespace liba

